配列やnull(特定のキーがない)があるJSONからjqでTSV作ってみた
jqを使ってJSONからTSVを作りたかったのですが、やり始めてから
このJSON、中に配列持ってるわ...。 あ、このkeyを持ってないレコードもあるし...。
と気付きました。 JSONは単なるKeyValueに限らず、Valueとして配列なども持つことができますし、 ファイルに含まれているレコードが全て同じKey項目を持っていることなどもちろん保証できません。 この辺を考慮できないと2次元のTSVに落とすことはできません。 最初は諦めてPythonのコードを書こうかと思ったのですが、 調べてみるとjqなら難なくできることがわかりました!
今回は、配列は単純にカンマ区切りの文字列にできるものとして、 配列を含むJSONからTSVを作る処理を行ってみました。
具体的には以下のようなJSONから
[ { "job": { "jobId": "sample_job_01", "targets": [ "aaa", "bbb", "ccc" ] } }, { "job": { "jobId": "sample_job_02" } }, { "job": { "jobId": "sample_job_03", "targets": [] } } ]
以下のような出力を得たいという要件です。
sample_job_01 aaa,bbb,ccc sample_job_02 sample_job_03
2,3行目の2カラム目には空文字を入れるようにします。
基本のおさらい
まずは基本から始めます。 特に何の変哲もなく、文字列が格納されている場合以下のようにできます。
[ { "job": { "jobId": "sample_job_01", "targets": "aaa,bbb,ccc" } } ]
$ cat test.json | jq -r '.[] | .job | [.jobId, .targets] | @tsv' sample_job_01 aaa,bbb,ccc
最後に@tsv
をするために、[.jobId, .targets]
で配列にする所がポイントですね。
配列を文字列に直す
次にtargets
を配列にしてみます。
[ { "job": { "jobId": "sample_job_01", "targets": ["aaa","bbb","ccc"] } } ]
連想配列オブジェクトの中に配列が入っているパターンです。 今回はコンマ区切りの文字列として出力することを目的としています。 この場合は以下のようにやればOKでした。
$ cat test.json | jq -r '.[] | .job | .targets |= join(",") | [.jobId, .targets] | @tsv' sample_job_01 aaa,bbb,ccc
ポイントは.targets |= join(",")
です。
=
による代入は.targets = "aaa"
のように文字列を代入などはできますが、
今回は.targets
を展開させたいので、Update-assignmentである|=
を使用します。
これは「.number |= +1
のように書ける」と言えばどんな挙動のものかはすぐにわかるかと思います。
join(",")
はおよそ見た通り、配列を,
で結合して文字列にする命令です。
これらを組み合わせることで、配列を文字列に変換できました。
nullがあるパターン
では最初に挙げた、.targets
が省略されているものもあるパターンです。
[ { "job": { "jobId": "sample_job_01", "targets": [ "aaa", "bbb", "ccc" ] } }, { "job": { "jobId": "sample_job_02" } }, { "job": { "jobId": "sample_job_03", "targets": [] } } ]
まずは先ほどのjqをそのまま実行してみます。
$ cat test.json | jq -r '.[] | .job | .targets |= join(",") | [.jobId, .targets] | @tsv' sample_job_01 aaa,bbb,ccc jq: error (at <stdin>:19): Cannot iterate over null (null)
出力を見てみると、最初の1行は出力されているのですが、 2行目で失敗して出力が止まっています。 スキップされるわけではなく中止されてしまうので3行目の出力もありません。 エラーの内容としては「nullは配列的に扱えないよ」という感じです。
では.targets
がnullのものにも適用できるようにしてみます。
$ cat test.json | jq -r '.[] | .job | if .targets then .targets |= join(",") else .targets = "" end | [.jobId, .targets] | @tsv' sample_job_01 aaa,bbb,ccc sample_job_02 sample_job_03
if
が出てきました!もうjqでなんでもできるな...。
ということで、if
で出力する内容を分岐できます。
if
の使い方は
if 条件式 then 命令 else 命令 end
です。
ここで命令
と書いた部分には、jqにおいて|
と|
の間に挟める文言を書けるようです。
if .targets then
の部分で.targets
がnullでないかを確認して、
null出なければ配列をjoinし、nullだった場合は""
で空文字列を出力するようにしています。
これで目的のTSVが出力されました!
まとめ
jqを使って、Valueとして配列を含み、かつnullもあり得るJSONから@tsv
でTSVとして出力するところまでできました!
改行がないとわかりにくいので、最後に改行を入れたコードを貼り付けておきます。 やり方がわかった上でこのコードを見ると特に違和感はないですね。
jq -r '.[] | .job | if .targets then .targets |= join(",") else .targets = "" end | [.jobId, .targets] | @tsv'
join
で単純な配列の文字列化と、if
で出力の場合わけができることがわかったので、
JSONから目的の形を取得したい時には広範囲に役立ちそうです!